`define AXI_BURST_TYPE_FIXED                                2'b00
`define AXI_BURST_TYPE_INCR                                 2'b01
`define AXI_BURST_TYPE_WRAP                                 2'b10
// Access permissions
`define AXI_PROT_UNPRIVILEGED_ACCESS                        3'b000
`define AXI_PROT_PRIVILEGED_ACCESS                          3'b001
`define AXI_PROT_SECURE_ACCESS                              3'b000
`define AXI_PROT_NON_SECURE_ACCESS                          3'b010
`define AXI_PROT_DATA_ACCESS                                3'b000
`define AXI_PROT_INSTRUCTION_ACCESS                         3'b100
// Memory types (AR)
`define AXI_ARCACHE_DEVICE_NON_BUFFERABLE                   4'b0000
`define AXI_ARCACHE_DEVICE_BUFFERABLE                       4'b0001
`define AXI_ARCACHE_NORMAL_NON_CACHEABLE_NON_BUFFERABLE     4'b0010
`define AXI_ARCACHE_NORMAL_NON_CACHEABLE_BUFFERABLE         4'b0011
`define AXI_ARCACHE_WRITE_THROUGH_NO_ALLOCATE               4'b1010
`define AXI_ARCACHE_WRITE_THROUGH_READ_ALLOCATE             4'b1110
`define AXI_ARCACHE_WRITE_THROUGH_WRITE_ALLOCATE            4'b1010
`define AXI_ARCACHE_WRITE_THROUGH_READ_AND_WRITE_ALLOCATE   4'b1110
`define AXI_ARCACHE_WRITE_BACK_NO_ALLOCATE                  4'b1011
`define AXI_ARCACHE_WRITE_BACK_READ_ALLOCATE                4'b1111
`define AXI_ARCACHE_WRITE_BACK_WRITE_ALLOCATE               4'b1011
`define AXI_ARCACHE_WRITE_BACK_READ_AND_WRITE_ALLOCATE      4'b1111
// Memory types (AW)
`define AXI_AWCACHE_DEVICE_NON_BUFFERABLE                   4'b0000
`define AXI_AWCACHE_DEVICE_BUFFERABLE                       4'b0001
`define AXI_AWCACHE_NORMAL_NON_CACHEABLE_NON_BUFFERABLE     4'b0010
`define AXI_AWCACHE_NORMAL_NON_CACHEABLE_BUFFERABLE         4'b0011
`define AXI_AWCACHE_WRITE_THROUGH_NO_ALLOCATE               4'b0110
`define AXI_AWCACHE_WRITE_THROUGH_READ_ALLOCATE             4'b0110
`define AXI_AWCACHE_WRITE_THROUGH_WRITE_ALLOCATE            4'b1110
`define AXI_AWCACHE_WRITE_THROUGH_READ_AND_WRITE_ALLOCATE   4'b1110
`define AXI_AWCACHE_WRITE_BACK_NO_ALLOCATE                  4'b0111
`define AXI_AWCACHE_WRITE_BACK_READ_ALLOCATE                4'b0111
`define AXI_AWCACHE_WRITE_BACK_WRITE_ALLOCATE               4'b1111
`define AXI_AWCACHE_WRITE_BACK_READ_AND_WRITE_ALLOCATE      4'b1111

`define AXI_SIZE_BYTES_1                                    3'b000
`define AXI_SIZE_BYTES_2                                    3'b001
`define AXI_SIZE_BYTES_4                                    3'b010
`define AXI_SIZE_BYTES_8                                    3'b011
`define AXI_SIZE_BYTES_16                                   3'b100
`define AXI_SIZE_BYTES_32                                   3'b101
`define AXI_SIZE_BYTES_64                                   3'b110
`define AXI_SIZE_BYTES_128                                  3'b111

module axi4_crtl(
    input ACLK,
    input ARESETn,
    input flush,

    input inst_r_ena,
    input [63:0] inst_addr,
    output reg [31:0] inst_r,
    input data_r_ena,
    input data_w_ena,
    input [63:0] data_addr,
    input [63:0] data_w,
    input [63:0] data_w_addr,
    input [7:0] data_w_mask,
    input wlast_i,
    output reg [63:0] data_r,

    output [3:0] ARID,
    output [63:0] ARADDR,
    output [7:0] ARLEN,
    output [2:0] ARSIZE,
    output [1:0] ARBURST,
    output [1:0] ARLOCK,
    output [3:0] ARCACHE,
    output [2:0] ARPROT,
    output [3:0] ARQOS,
    output [3:0] ARREGION,
    output ARVALID,
    input ARREADY,

    input [3:0] RID,
    input [63:0] RDATA,
    input RVALID,
    output RREADY,
    input RLAST,
    input [1:0] RRESP,

    output [3:0] AWID,
    output [63:0] AWADDR,
    output [7:0] AWLEN,
    output [2:0] AWSIZE,
    output [1:0] AWBURST,
    output [1:0] AWLOCK,
    output [3:0] AWCACHE,
    output [2:0] AWPROT,
    output [3:0] AWQOS,
    output [3:0] AWREGION,
    output AWVALID,
    input AWREADY,

    output [63:0] WDATA,
    output [7:0] WSTRB,
    output WVALID,
    input WREADY,
    output WLAST,

    input [3:0] BID,
    input BVALID,
    output BREADY,
    input [1:0] BRESP,

    output reg data_r_valid,
    output reg data_w_valid,
    output reg inst_r_valid
);

    parameter IDEL               = 3'b000;	//空闲
	parameter INST_SEND_RADDR    = 3'b001;	//指令cache发读地址
	parameter INST_RECEIVE_RDATA = 3'b010;	//指令cache接收读数据
	parameter DATA_SEND_RADDR    = 3'b011;	//数据cache发读地址
	parameter DATA_RECEIVE_RDATA = 3'b100;	//数据cache接收读数据
	parameter DATA_SEND_WADDR    = 3'b001;	//数据cache发写地址
	parameter DATA_SEND_WDATA    = 3'b010;	//数据cache发写数据
	parameter DATA_RECEIVE_B     = 3'b011;	//数据cache写响应

    wire ar_hs = ARREADY & ARVALID;
    wire r_hs = RREADY & RVALID;
    wire aw_hs = AWREADY & AWVALID;
    wire w_hs = WREADY & WVALID;
    wire b_hs = BREADY & BVALID;
    wire r_done = r_hs & RLAST;
    wire w_done = w_hs & WLAST;
	
	reg [2:0] axi_read_state;
    reg [2:0] axi_write_state;

    always @(posedge ACLK) begin
        if(ARESETn) begin
            axi_read_state <= 3'b000;
            data_r <= 64'd0;
            data_r_valid <= 1'b0;
            inst_r_valid <= 1'b0;
            inst_r <= 32'd0;
        end else begin
            case(axi_read_state)
                IDEL: begin
                    data_r <= 64'd0;
                    inst_r <= 32'd0;
                    if(flush) begin
                        axi_read_state <= IDEL;
                    end else if(inst_r_ena) begin
                        axi_read_state <= INST_SEND_RADDR;
                        inst_r_valid <= 1'b0;
                        data_r_valid <= 1'b0;
                    end else if(data_r_ena) begin
                        axi_read_state <= DATA_SEND_RADDR;
                        inst_r_valid <= 1'b0;
                        data_r_valid <= 1'b0;
                    end
                end
                INST_SEND_RADDR : begin
                    if(ar_hs) begin
                        axi_read_state <= INST_RECEIVE_RDATA;
                    end
                end
                INST_RECEIVE_RDATA : begin
                    if(r_done) begin
                        axi_read_state <= IDEL;
                        inst_r <= inst_addr[2] ? RDATA[63:32] : RDATA[31:0];
                        inst_r_valid <= 1'b1;
                    end
                end
                DATA_SEND_RADDR : begin
                    if(ar_hs) begin
                        axi_read_state <= DATA_RECEIVE_RDATA;
                    end
                end
                DATA_RECEIVE_RDATA : begin
                    if(r_done) begin
                        axi_read_state <= IDEL;
                        data_r <= RDATA;
                        data_r_valid <= 1'b1;
                    end
                end
                default : begin
                    
                end
            endcase
        end
    end

    always @(posedge ACLK) begin
        if(ARESETn) begin
            axi_write_state <= 3'b000;
        end else begin
            case(axi_write_state)
                IDEL: begin
                    data_w_valid <= 1'b0;
                    if(flush) begin
                        axi_write_state <= IDEL;
                    end else if(data_w_ena) begin
                        axi_write_state <= DATA_SEND_WADDR;
                    end
                end
                DATA_SEND_WADDR : begin
                    if(aw_hs) begin
                        axi_write_state <= DATA_SEND_WDATA;
                    end
                end
                DATA_SEND_WDATA : begin
                    if(w_done) begin
                        axi_write_state <= DATA_RECEIVE_B;
                    end
                end
                DATA_RECEIVE_B : begin
                    if(b_hs) begin
                        axi_write_state <= IDEL;
                        data_w_valid <= 1'b1;
                    end
                end
                default : begin
                    
                end
            endcase    
        end
    end
    ///////////////////////////////////////////////////
    wire aligned = data_addr[2:0] == 0;
    wire size_b             = 1'b0;
    wire size_h             = 1'b0;
    wire size_w             = 1'b0;
    wire size_d             = 1'b1;
    wire [3:0] addr_op1     = {{1{1'b0}}, data_addr[2:0]};
    wire [3:0] addr_op2     = ({4{size_b}} & {4'b0})
                                | ({4{size_h}} & {4'b1})
                                | ({4{size_w}} & {4'b11})
                                | ({4{size_d}} & {4'b111})
                                ;
    wire [3:0] addr_end     = addr_op1 + addr_op2;
    wire overstep           = addr_end[3] != 0;
    wire [7:0] axi_len      = aligned ? 0 : {{7{1'b0}}, overstep};

    wire [5:0] aligned_offset_l    = {{3{1'b0}}, {data_addr[2:0]}} << 3;
    wire [5:0] aligned_offset_h    = 64 - aligned_offset_l;
    wire [127:0] mask                  = (({128{size_b}} & {{120{1'b0}}, 8'hff})
                                                    | ({128{size_h}} & {{112{1'b0}}, 16'hffff})
                                                    | ({128{size_w}} & {{96{1'b0}}, 32'hffffffff})
                                                    | ({128{size_d}} & {{64{1'b0}}, 64'hffffffff_ffffffff})
                                                    ) << aligned_offset_l;
    wire [63:0] mask_l            = mask[63:0];
    wire [63:0] mask_h            = mask[127:63];

    reg [7:0] len;
    wire len_reset      = ARESETn | (b_hs & axi_write_state == IDEL);
    wire len_incr_en    = (len != axi_len) & (w_hs | r_hs);
    always @(posedge ACLK) begin
        if (len_reset) begin
            len <= 0;
        end
        else if (len_incr_en) begin
            len <= len + 1;
        end
    end

    wire [63:0] axi_r_data_l  = (RDATA & mask_l) >> aligned_offset_l;
    wire [63:0] axi_r_data_h  = (RDATA & mask_h) << aligned_offset_h;

    generate
        for (genvar i = 0; i < 1; i += 1) begin
            always @(posedge ACLK) begin
                if (ARESETn) begin
                    data_r[i*64+:64] <= 0;
                end
                else if (r_hs) begin
                    if (~aligned & overstep) begin
                        if (len[0]) begin
                            data_r[63:0] <= data_r[63:0] | axi_r_data_h;
                        end
                        else begin
                            data_r[63:0] <= axi_r_data_l;
                        end
                    end
                    else if (len == i) begin
                        data_r[i*64+:64] <= axi_r_data_l;
                    end
                end
            end
        end
    endgenerate
    ///////////////////////////////////////////////////

    assign ARID = 4'b0000;
    assign ARADDR = (axi_read_state == INST_SEND_RADDR || axi_read_state == INST_RECEIVE_RDATA) ? {inst_addr[63:3], {3{1'b0}}} : ((axi_read_state == DATA_SEND_RADDR || axi_read_state == DATA_RECEIVE_RDATA) ? {data_addr[63:3], {3{1'b0}}} : 64'd0);
    assign ARVALID = (axi_read_state == INST_SEND_RADDR || axi_read_state == DATA_SEND_RADDR) ? 1'b1 : 1'b0;
    assign ARLEN = 8'd0;
    assign ARSIZE = `AXI_SIZE_BYTES_8;
    assign ARBURST = `AXI_BURST_TYPE_INCR;
    assign ARLOCK = 1'b0;
    assign ARCACHE = `AXI_ARCACHE_NORMAL_NON_CACHEABLE_NON_BUFFERABLE;
    assign ARPROT = `AXI_PROT_UNPRIVILEGED_ACCESS;
    assign ARQOS = 4'b0000;
    assign ARREGION = 4'b0000;

    assign RREADY = (axi_read_state == INST_RECEIVE_RDATA || axi_read_state == DATA_RECEIVE_RDATA) ? 1'b1 : 1'b0;

    assign AWID = 4'b0000;
    assign AWADDR = (axi_write_state == DATA_SEND_WADDR || axi_write_state == DATA_SEND_WDATA) ? {data_w_addr[63:3], {3{1'b0}}} : 64'd0;
    assign AWLEN = 8'd0;
    assign AWSIZE = `AXI_SIZE_BYTES_8;
    assign AWBURST = `AXI_BURST_TYPE_INCR;
    assign AWLOCK = 1'b0;
    assign AWCACHE = `AXI_AWCACHE_NORMAL_NON_CACHEABLE_NON_BUFFERABLE;
    assign AWPROT = `AXI_PROT_UNPRIVILEGED_ACCESS;
    assign AWQOS = 4'b0000;
    assign AWREGION = 4'b0000;
    assign AWVALID = (axi_write_state == DATA_SEND_WADDR) ? 1'b1 : 1'b0;

    assign WDATA = (axi_write_state == DATA_SEND_WDATA) ? data_w : 64'd0;
    assign WSTRB = (axi_write_state == DATA_SEND_WDATA) ? data_w_mask : 8'b00000000;
    assign WVALID = (axi_write_state == DATA_SEND_WDATA) ? 1'b1 : 1'b0;
    assign WLAST = 1'b1;

    assign BREADY = (axi_write_state == DATA_RECEIVE_B) ? 1'b1 : 1'b0;
endmodule